Throttle.php

<?php

namespace Tlf\User;

trait Throttle {


    public function clear_stale_throttles(){
        $this->pdo->exec(
            "DELETE FROM throttle WHERE expires_at < NOW(6)"
        );
    }

    /**
     * microsleep() for the amount of time remaining on the throttle
     */
    public function throttle(string $key, string $actor){
        $remaining = $this->throttle_remaining($key, $actor);

        // var_dump($remaining);
        if ($remaining < 0)return;
        // exit;
        usleep($remaining * 1000);
    }

    /**
     * @param $key the action being throttled
     * @param $actor ip address or other identifier for who's being throttled
     *
     * @return milliseconds remaining on the throttle or 0 if no throttle
     */
    public function throttle_remaining(string $key, string $actor){
        // i need to subtract NOW() from expires_at
        // then convert that to milliseconds
        // and that's the amount of time remaining on the throttle
        //
        $start = microtime(true);
        $stmt = $this->pdo->prepare(

            "SELECT TIMESTAMPDIFF(MICROSECOND, NOW(6), MAX(expires_at)) / 1000 as remaining FROM throttle t
                WHERE `key` LIKE :key
                    AND actor LIKE :actor
                    AND expires_at > NOW(6)
            "
        );

        $stmt->execute([
            'key'=>$key,
            'actor'=>$actor
        ]);

        $mid = microtime(true);

        $rows = $stmt->fetchAll();
        $stmt->closeCursor();
        unset($stmt);
        if (count($rows)==0)return 0;
        if (count($rows)>1){
            throw new \Exception("I haven't yet handled the case of multiple throttles present in the db for the same key.");
        }

        // print_r($rows[0]);

        $end = microtime(true);


        // echo "\nFull:".(1000*($end - $start));
        // echo "\nMid:".(1000*($end - $mid));

        
        return (int)$rows[0]['remaining'];
    }

    public function add_throttle(string $key, string $actor, int $millis_from_now){
        $seconds = ((double)$millis_from_now)/1000;
        $stmt = $this->pdo->prepare(
            " INSERT INTO throttle (`key`, actor, expires_at)
                VALUES(:key, :actor, 
                    TIMESTAMPADD(SECOND, :seconds, NOW(6)) 
                ) ;
            "
        );
        $start = microtime(true);
        $stmt->execute([
            'key'=>$key,
            'actor'=>$actor,
            'seconds'=>$seconds
        ]);
        $end = microtime(true);

        // echo "\nInsert millis: ".(1000*($end - $start));

    }
}